도메인 주도 설계(2) - 값 객체

왜 중요한가?

  • 도메인 주도 설계의 도메인 모델을 객체화하는 가장 작은 단계
  • 가장 작은 단계의 기초를 알고 있어야 다음 내용에 대해서도 진행이 가능하다.

어떻게 활용할까?

  • 내가 사용하고 있는 코드들에 값 객체 개념을 적용한다.
  • 이전에는 이러한 개념을 인지하지 못해 클래스로 굳이 왜 바꾸지 싶었다.
  • 그러나 값 객체에 대한 내용을 파악했을 때 단순 객체가 아닌 class 객체로 표현했을 때의 장점에 대해 인지했다.

요약

  • 값 객체는 코드화된 도메인 개념
  • 값 객체가 가지는 값의 성질

    • 불변성
    • 교체 가능
    • 동등성 비교 가능
  • 값 객체의 장점

    • 자체 문서화
    • 값의 무결성
    • 중복 코드 방지
    • 행동 정의 가능

값 객체

값 객체는 도메인 지식을 코드화 한 것 이다.

값 객체는 데이터와 함께 데이터에 행해질 수 있는 액션들을 함께 가지고 있다.

값 객체는 말 그대로 값 + 객체를 의미한다. 따라서 값 객체는 값의 성질과 객체의 성질을 모두 만족해야 한다.

값의 성질

값 객체가 가지는 값이 가지는 속성은 어떤 것들이 있을까?

  • 불변성
  • 교체 가능성
  • 비교 가능성

불변성

값 객체는 불변성을 가지고 있다.

불변성이라는 것은 값 객체 내부 속성들이 변경 불가능하다는 의미이다. 불변성을 가진 값 객체는 속성을 바꾸고 싶더라도 변경할 수 없어야 한다.

만약 값 객체가 변경 가능하다면 데이터가 어떻게 변경될지 알 수 없기 때문에 예상치 못한 사이드 이펙트 등의 버그가 발생할 수 있다. 값 객체의 불변성은 버그를 줄이고 신뢰성을 높인다.

교체 가능성

그렇다면 값 객체를 변경하려면 어떻게 해야하는가? 새로운 값 객체를 생성하여 교체하면 된다.

불변 객체가 변경될 수 있는 방법은 새로운 값 변수를 만들어 교체하는 것이다.

값 객체는 속성을 변경해야 하는 경우 속성을 변경하는 것이 아니다. 새로운 속성을 가진 새로운 값 객체를 만들어 기존 값 객체와 교체하는 식으로 동작한다.

등가성 비교 가능

동일한 값 객체들끼리는 비교가 가능해야 한다. 여기서 비교는 크고 작음에 대한 비교가 아닌 등가성 비교를 의미한다.

비교에 대한 부분은 값 객체 내부에 비교에 대한 메서드를 선언해서 사용하는 방식으로 한다.

class Money {
  readonly amount: number
  readonly currency: string

  constructor(amount: number, currency: string) {
    this.amount = amount
    this.currency = currency
  }

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency
  }
}

const money1 = new Money(100, 'USD')
const money2 = new Money(100, 'USD')
const money3 = new Money(200, 'USD')

console.log(money1.equals(money2)) // true
console.log(money1.equals(money3)) // false

값 객체 외부에서 각 속성에 대한 비교로 값 객체를 비교하는 것은 피해야 한다.

값 객체의 장점

값 객체를 사용하지 않고 도메인 객체들을 원시값을 이용해서 표현하고 사용할 수도 있다. 원시값이 아닌 값 객체를 사용하는 방법은 코드양도 늘어나고 복잡한 면이 있다.

원시값은 범용적인 값이며 값 객체를 사용했을 때 확보할 수 있는 장점들이 있다. 값 객체를 사용했을 때 장점은 무엇일까?

자체 문서화

값 객체는 값 객체가 도메인 모델에 대한 문서화를 수행한다. 값 객체의 구조, 방어 코드, 액션 등이 해당 도메인 모델에 대한 지식이 없는 사람도 값 객체에 대한 지식을 습득할 수 있는 자체 문서가 된다.

반면 원시값을 사용하게 되면 별도의 문서나 주석을 달아야 하며 잘못 오용될 수 있는 여지도 있다.

무결성

도메인 모델 상 값이 가져야하는 형식, 제약이 있다.

원시값의 경우 값을 할당하거나 사용할 때 이러한 제약에 대한 검사를 거쳐야한다. 그렇지 않으면 예상치 못한 문제를 일으킬 수 있다.

값 객체를 사용하는 경우 자체적으로 형식, 제약에 대한 validation을 거칠 수 있다. 이는 개발자가 크게 신경쓰지 않고도 값의 무결성을 지킬 수 있다는 의미가 된다.

중복 코드 방지

값 객체는 해당 객체에 대한 검증, 비교, 액션에 대한 코드들을 가지고 있다. 값 객체에 관련된 코드들은 값 객체에 모아 둘 수 있기 때문에 효율적이다. 변경될 내용이 있더라도 값 객체 내부의 함수만 변경하면 된다.

원시값을 사용한다면 그 때 그 때 구현이 필요하게 된다. 동일한 도메인 모델에 대한 코드가 이 코드를 사용하게 되는 구석구석에 퍼져있게 된다. DRY 원칙을 위반하게 되는 것이다.

행동 정의 가능

특정 도메인 모델이 가능한 액션이 있고 불가능한 액션이 있을 것이다.

예를 들어, 돈을 나타내는 Money 객체가 있다고 해보자.

  • 가능: 1000원과 500원을 더하기
  • 불가능: 1000원과 500원을 곱하기

값 객체에서는 액션들을 구현하거나 구현하지 않음으로서 액션의 수행 가능 여부를 정의할 수 있다.

원시값을 사용하는 경우 액션 가능/불가능의 경계가 없으므로 오용될 여지가 있다.

참조


Written by@박대성

독서와 지식관리에 관심이 많은 개발자

GitHub